package com.onpositive.notes.parser;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import com.onpositive.notes.argumnets.ArgSet;
import com.onpositive.notes.argumnets.EntityList;
import com.onpositive.notes.argumnets.EntityListArgument;
import com.onpositive.notes.argumnets.IArgumentsSet;
import com.onpositive.notes.argumnets.MeasuredEntity;
import com.onpositive.notes.impl.BasicNote;
import com.onpositive.notes.impl.StringEntry;
import com.onpositive.notes.model.INote;
import com.onpositive.notes.model.INoteEntry;
import com.onpositive.notes.model.ITemplate;
import com.onpositive.notes.model.ITemplateArgument;
import com.onpositive.notes.model.ITemplateModel;
import com.onpositive.units.ParsedDate;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedScalar;

public class BasicNoteParser implements INoteParser {

	private static final long serialVersionUID = -7071191228843408538L;

	public BasicNoteParser() {
		super();
	}

	private DateParser dateParser = new DateParser() ;
	private DimensionParser dimensionParser = new DimensionParser() ;
	private ScalarParser scalarParser = new ScalarParser() ;
	
	@Override
	public INote parse(String str) {
		if( str == null )
			return null ;
		
		return internalParse( str ) ;
	}

	@Override
	public INote parse(String inputString, int start, int length) {
		
		if( inputString == null )
			return null ;
		
		int l = inputString.length();
		int s = Math.min( Math.max(0, start), l ) ;
		int t = Math.max( Math.min( start + length, l ), 0 ) ;
		
		if( t>=s )
			return null ;
		
		String str = inputString.substring(s,t) ;
		return internalParse( str ) ;
	}

	private INote internalParse(String str)
	{
		ArrayList<Block<?>> lst = extractBlocks(str);
		Date date = generateDate(lst) ;
		if( lst == null || lst.isEmpty() ){
			StringEntry singleEntry = new StringEntry(str,0,str.length()) ;
			ArrayList<INoteEntry> result = new ArrayList<INoteEntry>() ;
			result.add(singleEntry) ;
			return new BasicNote(result,lst,date,str) ;
		}		
		
		List<INoteEntry> templates = buildTemplates( str, lst ) ;
		List<INoteEntry> result = densifyNoteList( str, templates ) ;
		
		return new BasicNote(result,lst,date,str) ;
	}

	private Date generateDate(ArrayList<Block<?>> lst) {
		
		Date date = Calendar.getInstance().getTime() ;
		while(true){
			int dateInd = findNextBlock(lst, ParsedDate.class, 0, lst.size(), null, null, null, null) ;
			if( dateInd < 0 )
				break ;
		
			int templateInd = findNextBlock(lst, TemplateOccurence.class, 0, lst.size(), null, null,null,null) ;
			if( templateInd < 0 )
				templateInd = lst.size() ;
			
			if( templateInd < dateInd )				
				break ;
			
			Block<?> block = lst.get(dateInd);
			ParsedDate pDate = (ParsedDate) block.getVal();
			date = pDate.getUpperBound().getDate() ;
			break ;
		}
		return date;
	}

	private List<INoteEntry> densifyNoteList(String str, List<INoteEntry> templates) {
		
		ArrayList<INoteEntry> result = new ArrayList<INoteEntry>() ;
		if( templates == null ){
			result.add( new StringEntry(str, 0, str.length() )) ;
			return result ;
		}
		
		result.addAll( templates ) ;
		ArrayList<Integer> indList = new ArrayList<Integer>() ;
		indList.add(0) ;
		for( INoteEntry blk : templates ){
			indList.add( blk.getStart()) ;
			indList.add( blk.getEnd()) ;
		}
		indList.add(str.length()) ;
		for( int i = 0 ; i < indList.size() ; i += 2 ){
			
			Integer si = indList.get(i);
			Integer ei = indList.get(i+1);
			
			String s = str.substring(si, ei) ;

			StringEntry sEntry = new StringEntry(s, si, ei) ;
			result.add(sEntry) ;
		}
		Collections.sort( result ) ;
		return result ;
	}

	private void densifyBlockList(String str, ArrayList<Block<?>> lst) {
		ArrayList<Integer> indList = new ArrayList<Integer>() ;
		indList.add(0) ;
		for( Block<?> blk : lst ){
			indList.add( blk.getStart()) ;
			indList.add( blk.getEnd()) ;
		}
		indList.add(str.length()) ;
		for( int i = 0 ; i < indList.size() ; i += 2 ){
			
			Integer si = indList.get(i);
			Integer ei = indList.get(i+1);
			
			String s = str.substring(si, ei) ;//.trim() ;
			if(s.length() == 0)
				continue ;
			
			Block<String> sBlk = new Block<String>(str, si, ei, s, Block.BLOCK_KIND_STRING) ;
			lst.add(sBlk) ;
		}
		Collections.sort( lst ) ;
	}

	private List<INoteEntry> buildTemplates(String str, ArrayList<Block<?>> lst) {
		
		
		ArrayList<Integer> keyWordIndeces = findBlocks( lst, TemplateOccurence.class,  null, null ) ;
		if( keyWordIndeces.isEmpty() )
			return null ;
		
		String[] tags = new String[]{ SentenceSplitter.META_KEY_MARK_TAG } ;
		String[] tagValues = new String[]{ SentenceSplitter.MARK_TAG_LINE_BREAK } ;
		
		ArrayList<INoteEntry> result = new ArrayList<INoteEntry>() ; 
		int size = keyWordIndeces.size();
		for( int i = 0 ; i < size ; i++ ){
			
			int tStart = keyWordIndeces.get(i) ;			
			int nextLineBreak = findNextBlock( lst, Mark.class, tStart, lst.size(), tags, tagValues, null, null ) ;			
			if( nextLineBreak < 0 )
				nextLineBreak = lst.size() ;
			
			int nextKey = i < size-1 ? keyWordIndeces.get(i+1) : lst.size() ;
			
			int tEnd = Math.min(nextKey, nextLineBreak) ;
//				continue ;
			
			TemplateOccurence to = (TemplateOccurence) lst.get(tStart).getVal() ;
			ITemplateModel<?> tModel = to.getModel();
			ITemplate template = null ;
			//List<ITemplateArgument<?>> tArgs = template.getArguments();
			
			if( tModel.getArgumentModels().size() == 0 ){
				template = tModel.createInstance() ;
				template.setStart(lst.get(tStart).getStart()) ;
				template.setEnd(lst.get(tStart).getEnd()) ;
				result.add(template) ;
				continue ;
			}
			
			ArrayList<Integer> entityBounds = markArgSets(lst, tStart, tEnd);
			int end = 0 ;
l0:			for( int j = 0 ; j < entityBounds.size()-1 ; j++ ){
				
				int eStart = entityBounds.get(j) ;
				int eEnd = entityBounds.get(j+1) ;
				
				List<ITemplateArgument<?>> tArgs = tModel.initArguments() ;
				ArrayList<ITemplateArgument<?>> parsedArgs = new ArrayList<ITemplateArgument<?>>() ;
				ArrayList<Block<?>> involvedBlocks = new ArrayList<Block<?>>() ;
				
				for( ITemplateArgument<?> tArg : tArgs ){
					Class<?> subjectClass = tArg.getSubjectClass() ;
					
					int ind = findNextBlock( lst, subjectClass, eStart, eEnd, null, null, tModel, tArg ) ;
					if( ind < 0 ){
						if( tArg.isRequired() )
							continue l0 ;
						else
							continue ;
					}
						
					Block<?> blk = lst.get(ind);
					Object obj = blk.getVal() ;
					tArg.setValue(obj) ;
					parsedArgs.add(tArg) ;
					involvedBlocks.add(blk) ;
				}
				
				for( Block<?> blk : involvedBlocks )					
					end = Math.max(end, blk.getEnd()) ;
				
				if( template == null )
					template = tModel.createInstance() ;
				
				IArgumentsSet argSet = new ArgSet( parsedArgs ) ;
				template.addArgSet( argSet ) ;
			}
			if( template == null )
				continue ;
			
			template.setStart(lst.get(tStart).getStart()) ;
			template.setEnd( end ) ;
			result.add(template) ;			
		}

		return result;
	}

	private EntityList extractEntityList(String str, ArrayList<Block<?>> lst, int start, int end) {
		
		ArrayList<Integer> entityBounds = markArgSets(lst, start, end);
		
		ArrayList<MeasuredEntity> meLst = new ArrayList<MeasuredEntity>() ;
		for( int i = 0 ; i < entityBounds.size()-1 ; i++ ){
			
			int eStart = entityBounds.get(i) ;
			int eEnd = entityBounds.get(i+1) ;
			MeasuredEntity me = buildMesuredEntity( str, lst, eStart, eEnd ) ;
			if( me == null )
				continue ;
			
			meLst.add(me) ;			
		}
		EntityList result = new EntityList(meLst) ;
		return result;
	}

	public ArrayList<Integer> markArgSets(ArrayList<Block<?>> lst, int start,
			int end) {
		String[] tags = new String[]{SentenceSplitter.META_KEY_MARK_TAG};
		String[] tagValues = new String[]{SentenceSplitter.MARK_TAG_COMA};
		ArrayList<Integer> comaPositions = findBlocks(lst, Mark.class, start, end, tags, tagValues);
		
		ArrayList<Integer> entityBounds = new ArrayList<Integer>() ;
		entityBounds.add(start) ;
		entityBounds.addAll(comaPositions) ;
		entityBounds.add(end) ;
		return entityBounds;
	}

	private MeasuredEntity buildMesuredEntity( String str, ArrayList<Block<?>> lst, int start, int end) {
		
		int dimInd = findNextBlock(lst, ParsedDimension.class, start, end, null, null, null, null) ;
		if( dimInd < 0 )
			return null ;
		
		ParsedDimension dim = (ParsedDimension) lst.get(dimInd).getVal();
		
		//TODO find ident here
		
		ArrayList<Integer> strIndeces = findBlocks(lst, String.class, start, end, null, null) ;
		if(strIndeces.isEmpty())
			return null;
		
		String s = null ;
		for( int i : strIndeces ){
			Object val = lst.get(i).getVal();
			String s_ = val.toString() ;
			if(s_.trim().length() == 0)
				continue ;
			
			s = s_.trim() ;
			break ;
		}
		
		MeasuredEntity result = new MeasuredEntity( s, dim) ;
		return result;
	}

	private int findNextBlock(
			ArrayList<Block<?>> lst,
			Class<?> clazz,
			int start,
			int end,
			String[] tags,
			String[] tagValues,
			ITemplateModel<?> tModel,
			ITemplateArgument<?> tArg) {
		
		
l0:		for( int i = start ; i < end ; i++ ){
			Block<?> blk = lst.get(i) ;
			Object value = blk.getVal();
			if( !clazz.isInstance(value) )
				continue ;
			
			if( tModel != null && tArg != null ){
				if( !tModel.validate( tArg.getModel() , value) )
					continue l0;
			}
		
			if( tags != null && tagValues != null ){
				int l = Math.min( tags.length, tagValues.length) ;	
				for( int j = 0 ; j < l ; j++ ){
						
					String tag = tags[j];
					String requiredVal = tagValues[j] ;
					if( tag == null || requiredVal == null )
						continue ;
						
					String tagVal = blk.getMetaInfo(tag, String.class) ;
					if( !requiredVal.equals(tagVal) )
						continue l0;
				}
			}
			return i ;		
		}
		return -1 ;
	}

	private ArrayList<Integer> findBlocks(ArrayList<Block<?>> lst, Class<?> clazz, String[] tags, String tagValues[]) {		
		return findBlocks(lst, clazz, 0, lst.size(), tags, tagValues);
	}

	private ArrayList<Integer> findBlocks(
			ArrayList<Block<?>> lst,
			Class<?> clazz,
			int start,
			int end,
			String[] tags,
			String tagValues[] )
	{
		ArrayList<Integer> result = new ArrayList<Integer>() ;
		
		if( tags == null || tagValues == null){
			for( int i = start ; i < end ; i++ ){
				Block<?> blk = lst.get(i) ;
				if( !clazz.isInstance(blk.getVal()) )
					continue ;
				
				result.add(i) ;
			}
		}
		else{
			int l = Math.min( tags.length, tagValues.length) ;
l0:			for( int i = start ; i < end ; i++ ){
				Block<?> blk = lst.get(i) ;
				if( !clazz.isInstance(blk.getVal()) )
					continue ;
				
				for( int j = 0 ; j < l ; j++ ){
					
					String tag = tags[j];
					String requiredVal = tagValues[j] ;
					if( tag == null || requiredVal == null )
						continue ;
					
					String tagVal = blk.getMetaInfo(tag, String.class) ;
					if( !requiredVal.equals(tagVal) )
						continue l0;
				}
				result.add(i) ;			
			}
		}
		return result;
	}

	public ArrayList<Block<?>> extractBlocks(String str) {
		
		if(str == null)
			return null ;
		
		int l = str.length();
		int delimeterEnd = l+1 ;
		HashMap<String,Object> meta = new HashMap<String, Object>() ;
		
		long t0 = System.currentTimeMillis(), t1 ;
		ArrayList<Block<TemplateOccurence>> templates = KeyWordParser.detectKeyWords(str) ;
		t1 = System.currentTimeMillis() ;
		System.out.println( "templates: " + (t1-t0) ) ;		
		
		t0=t1;	
		ArrayList<Block<Mark>> lineBreaks = SentenceSplitter.extractMarks(str);
		t1 = System.currentTimeMillis() ;
		System.out.println( "marks: " + (t1-t0) ) ;		
		
		t0=t1;	
		ArrayList<Block<ParsedDate>> dates = dateParser.parseNonRangeValues(str, 0, l, delimeterEnd, meta, "BasicNoteParser");
		t1 = System.currentTimeMillis() ;
		System.out.println( "dates: " + (t1-t0) ) ;		
		
		t0=t1;	
		ArrayList<Block<ParsedDimension>> dims = dimensionParser.parseNonRangeValues(str, 0, l, delimeterEnd, meta, "BasicNoteParser");
		t1 = System.currentTimeMillis() ;
		System.out.println( "dimensions: " + (t1-t0) ) ;		
		
		
		
		
		ArrayList<Block<?>> lst = new ArrayList<Block<?>>() ;
		if( templates != null )
			for( Block<TemplateOccurence> blk : templates )
				lst.add(blk) ;
		
		if( lineBreaks != null )
			for( Block<?> blk : lineBreaks )
				lst.add(blk) ;
		
		if( dates != null ){
			for( Block<ParsedDate> blk : dates )
				if( blk.getSubstring().replaceAll("\\d", "").trim().length() != 0 )
					lst.add(blk) ;
		}
		
		if( dims != null )
			for( Block<ParsedDimension> blk : dims )
				lst.add(blk) ;
		
		boolean[] mask = new boolean[l] ;		
		for( Block<?> blk : lst ){
			for( int i = blk.getStart(); i < blk.getEnd() ; i++ )
				mask[i] = true ;
		}
		meta.put(ScalarParser.OCCUPIED_POSITIONS, mask) ;		
		
		t0 = System.currentTimeMillis() ;
		
		ArrayList<Block<ParsedScalar>> scalars = scalarParser.parseNonRangeValues(str, 0, l, delimeterEnd, meta, "BasicNoteParser");
		
		t1 = System.currentTimeMillis() ;
		System.out.println( "scalars: " + (t1-t0) ) ;		
		t0=t1;	
		
		if(scalars != null)
l0:			for( Block<ParsedScalar> blk : scalars ){
				for( int i = blk.getStart(); i < blk.getEnd() ; i++ )
					if( mask[i] ) 
						continue l0 ;
			
			lst.add(blk) ;			
		}
		
		Collections.sort(lst) ;
		densifyBlockList(str,lst) ;
		return lst;
	}	
}
